package com.jwcao.common.scan.object;

import com.esotericsoftware.reflectasm.MethodAccess;
import com.jwcao.common.utils.ReflectUtil;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import lombok.extern.slf4j.Slf4j;

/**
 * name: FieldAccessMethod description: TODO
 *
 * @author caojingwei date: 2023/3/22 15:41
 */
@Slf4j
public enum FieldAccessMethod {
    NATIVE {
        @Override
        public Object get(ScanField scanField, Object target) {
            try {
                return ReflectUtil.getInheritedDeclaredFieldValue(target, scanField.getField());
            } catch (NoSuchFieldException e) {
                log.error("native get error", e);
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                log.error("native get error", e);
                throw new RuntimeException(e);
            }
        }

        @Override
        public void set(ScanField scanField, Object target, Object value) {
            try {
                ReflectUtil.setInheritedDeclaredFieldValue(target, scanField.getField(), value);
            } catch (NoSuchFieldException e) {
                log.error("native set error", e);
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                log.error("native set error", e);
                throw new RuntimeException(e);
            }
        }

        @Override
        public int getterIndex(ScanField scanField) {
            return 0;
        }

        @Override
        public int setterIndex(ScanField scanField) {
            return 0;
        }
    }, ASM_FIELD {
        @Override
        public Object get(ScanField scanField, Object target) {
            return scanField.getFieldAccess().get(target, scanField.getGetterIndex());
        }

        @Override
        public void set(ScanField scanField, Object target, Object value) {
            scanField.getFieldAccess().set(target, scanField.getSetterIndex(), value);
        }

        @Override
        public int getterIndex(ScanField scanField) {
            return scanField.getFieldAccess().getIndex(scanField.getField());
        }

        @Override
        public int setterIndex(ScanField scanField) {
            return scanField.getFieldAccess().getIndex(scanField.getField());
        }
    }, ASM_METHOD {
        @Override
        public Object get(ScanField scanField, Object target) {
            return scanField.getMethodAccess().invoke(target, scanField.getGetterIndex());
        }

        @Override
        public void set(ScanField scanField, Object target, Object value) {
            scanField.getMethodAccess().invoke(target, scanField.getSetterIndex(), value);
        }

        @Override
        public int getterIndex(ScanField scanField) {
            String name = ReflectUtil.getterName(scanField.getName());
            return scanField.getMethodAccess().getIndex(name);
        }

        @Override
        public int setterIndex(ScanField scanField) {
            String name = ReflectUtil.setterName(scanField.getName());
            return scanField.getMethodAccess().getIndex(name);
        }
    };

    public abstract Object get(ScanField scanField, Object target);

    public abstract void set(ScanField scanField, Object target, Object value);

    public abstract int getterIndex(ScanField scanField);

    public abstract int setterIndex(ScanField scanField);

    public static <T> FieldAccessMethod judgeGetter(Field field, MethodAccess methodAccess) {
        if (Modifier.isPublic(field.getModifiers())) {
            return ASM_FIELD;
        }

        try {
            methodAccess.getIndex(ReflectUtil.getterName(field.getName()));
            return ASM_METHOD;
        } catch (Exception e) {
            return NATIVE;
        }
    }

    public static <T> FieldAccessMethod judgeSetter(Field field, MethodAccess methodAccess) {
        if (Modifier.isPublic(field.getModifiers())) {
            return ASM_FIELD;
        }

        try {
            methodAccess.getIndex(ReflectUtil.setterName(field.getName()));
            return ASM_METHOD;
        } catch (Exception e) {
            return NATIVE;
        }
    }
}
